<?php

namespace slinks\extensions\container;

use slinks\core\Config\Resource\FileResource;
use slinks\common\io\AnnotationReader;
use slinks\core\DependencyInjection\Extension\Extension;
use slinks\common\Finder\Finder;
use slinks\core\DependencyInjection\ContainerBuilder;
use slinks\core\dependencyInjectionExt\annotation\ServiceDefiningAnnotationVisitor;
use slinks\core\DependencyInjection\Loader\XmlFileLoader;
use slinks\common\io\FileSystemReader;
use slinks\core\dependencyInjectionExt\ContainerUtil;

/**
 * Adds some new ways to define services for the container.
 * @author Navid Mitchell
 */
class ContainerExtension extends Extension {

    /**
     * The application is a hybrid container extension point. It allows you to configure services, extensions and compilers in one shot.
     * This basically mirros that of the main Slinks application setup, except the application will be configured as a "sub application" of the root application.
     * This allows for applications to include other applications as "sub applications" if they need to exist within the same container.
     * 
     * Usage: 
     * 
     *      <container:applicationScan path="/path/to/applications" />
     *      
     *      path = the path to search for {application}.services.xml that define the applications.
     *             if more than one directory is desired they can be seperated by commas. 
     *      depth = optional parameter that defines how deep the folder structure will be searched.
     *      fileSuffix = optional parameter that defines the file suffix that will be used when looking for application definitions.
     *                   defaults to .config.xml. This means all application files must be named {application}.services.xml. 
     *                   This can be changed by changing the value of this parameter.
     *      autoRegisterNamespace = optional parameter if true will automatically register a namespace with the root directory as the namespace prefix. Default is true.
     *                              Basically this removes the need to do this for every application. 
     *                              However this also imposes that all application code's root namespace must be the same as the application directory name. 
     *                              
     * <code>
     *                              $loader = new \slinks\common\UniversalClassLoader();
     *
     *                              $internalNamespaces = array(
     *                                      'applicationDirectoryName'         =>  'applicationDirectoryPath' // Note this info would be replaced with the application info.
     *                                      );
     *                              $loader->registerNamespaces(array_merge($internalNamespaces,$this->classNamespaces));
     *
     *                              $loader->register();
     * </code>
     *                              @see slinks\common\UniversalClassLoader for more information.
     * 
     * 
     * The typical directory structure for a application is as follows. It is good practice to name the application directory and the application service file the same.
     * 
     * {application}
     * --extensions
     * ----myextension
     * ------compiler
     * --------CompilerImplementation.php
     * ----ExtensionImplementation.php
     * --{application}.config.xml
     * 
     * The extensions directory contains any container extensions that need to be added. 
     * An extension can define additional compilers as well the compilers must be contained within the extensions/compiler directory.
     * The extensions and compiler directories and implementations can be ommited. 
     *
     * The only requirement is that there is a {application}.services.xml file within each directory that has a application. 
     * {application} automatically becomes the name of the application when needed for any of the other utility methods.
     * 
     * When defining a application certain parameters are automatically registered which are then available to the user.
     * 
     * Parameters Available:
     * 
     * {application}.root.dir = points to the directory where the {application}.config.xml is found.
     * 
     * @param type $config conti
     * @param ContainerBuilder $container 
     */
    public function applicationScanLoad($config, ContainerBuilder $container) {
        if (isset($config['path']) && $config['path'] != null) {

            $path = preg_split('/,/', $config['path']);
            // reg ex for anything + filesuffix
            $fileSuffix = (isset($config['fileSuffix']) ? $config['fileSuffix'] : '.config.xml');
            $autoRegisterNamespace = (isset($config['autoRegisterNamespace']) ? $config['autoRegisterNamespace'] : true);
            $depth = null;
            if (isset($config['depth'])) {
                $depth = $config['depth'];
            }

            ContainerUtil::addApplications($container, $path, $autoRegisterNamespace, $fileSuffix, $depth);
        }
    }

    /**
     * Loads services from annotations using the specified paths.
     *
     * Usage example:
     *
     *      <container:annotationScan path="/path/to/services" />
     * 
     *      path = the path to search for annotated service files with service definitions.
     *      depth = optional parameter that defines how deep the folder structure will be searched.
     *
     * @param array $config An array of configuration settings
     * @param ContainerBuilder $container A ContainerBuilder instance
     */
    public function annotationScanLoad($config, ContainerBuilder $container) {
        if (isset($config['path']) && $config['path'] != null) {
            $path = $config['path'];

            $reader = new AnnotationReader(new ServiceDefiningAnnotationVisitor($container));

            $finder = new Finder();
            if (isset($config['depth'])) {
                $reader->read($finder->files()->depth('< ' . $config['depth'])->name('*.php')->in($path));
            } else {
                $reader->read($finder->files()->name('*.php')->in($path));
            }
        }
    }

    /**
     * Loads services from xml using the specified paths. To find any .xml files and scan them for services.
     * This is more lowlevel than that of applicationLoad. This can be used when more user control is desired. 
     *
     * Usage example:
     * 
     *      <container:xmlScanLoad path="/path/to/xmlServiceDefinitions" depth="3" filename="services.xml"/>
     * 
     *      path = the path to search for services.xml files with service definitions.
     *      depth = optional parameter that defines how deep the folder structure will be searched.
     *      filename = optional parameter that defines the name of the services file to look for. Defaults to services.xml. 
     *                
     * @param array $config An array of configuration settings
     * @param ContainerBuilder $container A ContainerBuilder instance
     */
    public function xmlScanLoad($config, ContainerBuilder $container) {
        if (isset($config['path']) && $config['path'] != null) {
            $path = $config['path'];
            $filename = (isset($config['filename']) ? $config['filename'] : 'services.xml');

            $reader = new FileSystemReader(new XmlServiceFileVisitor($container));

            $finder = new Finder();
            if (isset($config['depth'])) {
                $reader->read($finder->files()->depth('< ' . $config['depth'])->name($filename)->in($path));
            } else {
                $reader->read($finder->files()->name($filename)->in($path));
            }
        }
    }

    /**
     * Returns the base path for the XSD files.
     *
     * @return string The XSD base path
     */
    public function getXsdValidationBasePath() {
        return __DIR__ . '/schema';
    }

    /**
     * Returns the namespace to be used for this extension (XML namespace).
     *
     * @return string The XML namespace
     */
    public function getNamespace() {
        return 'http://www.mitchellsoftware.net/schema/dic/container';
    }

    /**
     * Returns the recommended alias to use in XML.
     *
     * This alias is also the mandatory prefix to use when using YAML.
     *
     * @return string The alias
     */
    public function getAlias() {
        return 'container';
    }

}

